/********************************************************************* 
 *					SRLOS Team Allright Reserved
 *@filename: thread.c
 *@autor:	bloceanc
 *@date:	07/27/2010
 *@note:	used for thread mangerment
 *********************************************************************/

#include "commdef.h"
#include "task_config.h"
#include "task.h"


// address of thread return when thread run first time after create
extern unsigned int e_thread_start_addr;
extern k_thread *g_current_thread;
extern k_process *g_current_process;

extern int mt_mutex_lock(k_mutex *mutex, long long time);
extern void mt_mutex_unlock(k_mutex *mutex);
extern int mt_mutex_wait_remove_thread(k_mutex *mutex, k_thread *thread);

extern void *kernel_allocate(unsigned int size);
extern void kernel_free(void *address);

extern void *__mmu_os_virtual_allocate(unsigned int page_count);
extern void __mmu_os_vritual_free(unsigned int page_viraddr, unsigned int page_count);

extern void comm_global_critical_section_enter(void);
extern void comm_global_critical_section_leave(void);

extern void task_schedule(k_thread *thread);
extern void task_process_terminate(k_process *process);

extern void task_tid_release(unsigned int id);
extern long long get_tick_count(void);

/*
 * 	insert thread to list
 * 	NOTE: insert with order of current priority of thread, like process.
 */
static void s_task_thread_insert_list(k_thread **thread_list, k_thread *thread, k_mutex *thread_lock)
{
	k_thread *tmp = NULL;
	k_thread *last = NULL;

	mt_mutex_lock(thread_lock, -1);
	if(*thread_list == NULL)
	{
		thread->next = NULL;
		thread->prev = NULL;
		*thread_list = thread;
		mt_mutex_unlock(thread_lock);
		return;
	}
	
	tmp = *thread_list;
	while(tmp)
	{
		if(tmp->cur_priority < thread->cur_priority)
		{
			break;
		}
		last = tmp;
		tmp = tmp->next;
	}
	
	if(tmp)
	{
		// insert.
		if(tmp->prev == NULL)
		{
			// this is the first one in list.
			thread->prev = NULL;
			thread->next = tmp;
			tmp->prev = thread;
		}
		else
		{
			thread->prev = tmp->prev;
			tmp->prev->next = thread;
			tmp->prev = thread;
			thread->next = tmp;
		}
	}
	else
	{
		last->next = thread;
		thread->next = NULL;
		thread->prev = last;
	}
	mt_mutex_unlock(thread_lock);
}

/*
 * 	remove thread from a list
 */
static void s_task_thread_remove_list(k_thread **thread_list, k_thread *thread, k_mutex *thread_lock)
{
	mt_mutex_lock(thread_lock, -1);
	if(thread->next == NULL && thread->prev == NULL)
	{
		// the only one
		*thread_list = NULL;
	}else if(thread->next == NULL && thread->prev)
	{
		// the last one
		thread->prev->next = NULL;
	}else if(thread->next && thread->prev)
	{
		// normal
		thread->prev->next = thread->next;
		thread->next->prev = thread->prev;
	}else if(thread->next && thread->prev == NULL)
	{
		// first one
		(*thread_list) = thread->next;
		(*thread_list)->prev = NULL;
	}
	thread->next = NULL;
	thread->prev = NULL;
	mt_mutex_unlock(thread_lock);
}

/**
 * Create CPU Interrupt Context For new Thread
 * @param stack_top_addr address of stack top
 * @param addr_return return address, as LR
 * @param thread_spsr SPSR for new thread
 * @param usr_stack_top user mode stack top pointer
 * @return the stack pointer. we use STMFD and LDMFD, so this pointer point to the element in stack top.
 * @NOTE: we fill R0~R12 and R13_usr and R14_usr as default value 0
 *		Read <srlos interrupt design> document for more detials.
 */
static unsigned int *task_thread_initialize_kernel_stack(unsigned int *stack_top_addr, unsigned int addr_return, unsigned int usr_stack_top)
{
	unsigned int i = 0;

	if(stack_top_addr == NULL)
		return 0;
	
	// push return address
	(*--stack_top_addr) = addr_return;
	
	// push SPSR, user mode cpsr value!
	if(addr_return & 0x3)
	{
		// Thumb code
		(*--stack_top_addr) = 0x30;
	}
	else
	{
		// ARM code
		(*--stack_top_addr) = 0x10;
	}

	// push R0~R12
	for(i = 0; i < 13; i++)
	{
		(*--stack_top_addr) = 0;
	}

	// push R13_usr 
	(*--stack_top_addr) = usr_stack_top;
	// putsh R14_usr
	(*--stack_top_addr) = 0;

	return stack_top_addr;
}

/*
 * Initialize Schedule Context for new thread
 * @param		sys_sp	top of kernel mode stack 
 */
static unsigned int *task_thread_initialize_cpu_context(unsigned int *sys_sp, unsigned int ret_addr)
{
	unsigned int i;
	
	// R15, pc
	(*--sys_sp) = ret_addr;
	
	// R0 ~ R12, R14
	for(i = 0; i <= 13; i++)
		(*--sys_sp) = 0;
		
	// cpsr, here, we should use a default value, system mode, and enable interrupt, and use ARM ISET
	(*--sys_sp) = 0x1f;
	return sys_sp;
}

/*
 * 	Initialize Thread Context Block
 */
void task_thread_initTCB(k_thread *thread)
{
	thread->cur_priority = 0;
	thread->fix_priority = 0;
	thread->kernel_sp = 0;
	thread->mutex_wait = NULL;
	thread->mutex_depth = 0;
	thread->mutex_overtime = 0;
	thread->next = NULL;
	thread->prev = NULL;
	thread->id = 0;
	thread->state = TASK_THREAD_STATE_CREATE;
	thread->sys_stack = 0;
	thread->usr_stack = 0;
	thread->wait_next = NULL;
	thread->wait_prev = NULL;
	thread->alamtime = 0;
}

/*
 *	insert thread to a specific list in its process
 * add remove from current list
 * NOTE:	this function will NOT DO schedule!
 */
void task_thread_insert(k_thread *thread, unsigned int state)
{
	if(thread->state == state)
		return;	// do nothing.
	
	// must remove from old list first.otherwise the next and prev fields in thread will be error!
	switch(thread->state)
	{
		case TASK_THREAD_STATE_ALARM:
		{
			s_task_thread_remove_list(&thread->process->thread_waitting, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_WAITTING:
		{
			s_task_thread_remove_list(&thread->process->thread_waitting, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_SUSPEND:
		{
			s_task_thread_remove_list(&thread->process->thread_suspend, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_RUNABLE:
		{
			s_task_thread_remove_list(&thread->process->thread_runable, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_RUNNING:
		{
			s_task_thread_remove_list(&thread->process->thread_running, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_CREATE:	// we must do nothing, because it is new, so it doesn't belong to any list!
		break;	// do nothing
		default:
			return;	// some errors!
	}
	
	// sencond insert to list
	thread->state = state;
	switch(state)
	{
		case TASK_THREAD_STATE_ALARM:
		{
			s_task_thread_insert_list(&thread->process->thread_waitting, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_WAITTING:
		{
			s_task_thread_insert_list(&thread->process->thread_waitting, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_SUSPEND:
		{
			s_task_thread_insert_list(&thread->process->thread_suspend, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_RUNABLE:
		{
			s_task_thread_insert_list(&thread->process->thread_runable, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_RUNNING:
		{
			s_task_thread_insert_list(&thread->process->thread_running, thread, &thread->process->thread_mutex);
		}
		break;
		case TASK_THREAD_STATE_TERMINATE:
		{
			s_task_thread_insert_list(&thread->process->thread_terminating, thread, &thread->process->thread_mutex);
		}
		break;
		default:
			return;	// some errors
	}
}


/*
 * create new thread 
 * @param priority			thread priority
 * @param code_addr			thread running start address
 * @param usr_stack			user mode stack address
 * @NOTE: About usr_stack, this address is the top of the stack address.so, caller maybe need
 * 		move the pointer to top of stack!
 * @WARNING:
 *		!###### This routine only can be used by kernel... Any other shoud use next routine following! #########!!!
 */
k_thread *task_createthread(unsigned int priority, unsigned int code_addr, unsigned int *usr_stack)
{
	unsigned int *sys_stack = NULL;
	k_thread *thread = NULL;
	
	sys_stack = __mmu_os_virtual_allocate(TASK_THREAD_SYS_STACK_SIZE>>12);
	if(sys_stack == NULL)
	{
		return NULL;
	}
	
	thread = kernel_allocate(sizeof(k_thread));
	if(thread == NULL)
	{
		__mmu_os_vritual_free((unsigned int)sys_stack, TASK_THREAD_SYS_STACK_SIZE>>12);
		return NULL;
	}
	
	// initialize TCB
	task_thread_initTCB(thread);
	thread->sys_stack = (unsigned int)sys_stack;	// set this before value changed
	thread->usr_stack = (unsigned int)usr_stack;
	
	sys_stack = (unsigned int *)(((unsigned int)sys_stack) + TASK_THREAD_SYS_STACK_SIZE); // we should make top pointer point to top of stack!

	// initialize kernel mode stack
	thread->kernel_sp = (unsigned int)task_thread_initialize_kernel_stack((unsigned int *)sys_stack, code_addr, (unsigned int)usr_stack);
	// initialize cpu context. when scheduled will use this
	thread->kernel_sp = (unsigned int)task_thread_initialize_cpu_context((unsigned int *)thread->kernel_sp, e_thread_start_addr);

	thread->fix_priority = priority;
	thread->state = TASK_THREAD_STATE_CREATE;
	thread->id = task_tid();
	thread->type = TASK_THREAD_TYPE_NORMAL;

	return thread;
}

/*
 *  create a thread run in specific process with initialize state
 *@return threadid
 *@param create_flag:  0 means suspend new thread after created, otherwise means runable after created
 */
int task_thread_create_pro(k_process *process, unsigned int priority, unsigned int code_addr, unsigned int *usr_stack, int create_flag)
{
	k_thread *newthread = NULL;
	
	newthread = task_createthread(priority, code_addr, usr_stack);
	if(newthread == NULL)
		return -1;

	// #############################################################
	// we must set state to CREATE, because we must indicate it 
	// doesn't belong to any list, we can't delete it from any list!!!!
	// ############################################################
	newthread->state = TASK_THREAD_STATE_CREATE;
	if(create_flag)
	{
		s_task_thread_insert_list(&process->thread_runable, newthread, &process->thread_mutex);
	}
	else
	{
		s_task_thread_insert_list(&process->thread_suspend, newthread, &process->thread_mutex);
	}

	return newthread->id;
}

/*
 *	terminate a thread in current process
 */
void task_thread_terminate(unsigned int hthread)
{
	k_thread *thread = NULL;
	
	if(hthread == 0)
		return;
		
	thread = (k_thread *)hthread;
	mt_mutex_lock(&g_current_process->thread_mutex,-1);
	// we just move thread to terminate queue, scheduler will do free TCB!
	task_thread_insert(thread, TASK_THREAD_STATE_TERMINATE);
	mt_mutex_unlock(&g_current_process->thread_mutex);
	if(g_current_thread == thread)
	{
		// must do schedule after unlock thread mutex, otherwise will be death lock!
		task_schedule(NULL);		
	}
}

/*
 * release waitting mutex in thread.
 *NOTE: this function just remove the thread from waitting list of mutex waitting.
 */
static void s_task_thread_free_wait(k_thread *thread)
{
	/// we need global lock, because mutex if global resouce.
	if(thread->state == TASK_THREAD_STATE_WAITTING)
	{
		comm_global_critical_section_enter();
		if(thread->mutex_wait)
		{
			mt_mutex_wait_remove_thread(thread->mutex_wait, thread);
			thread->mutex_wait = NULL;
			thread->mutex_depth = 0;
			thread->mutex_overtime = 0;
		}
		comm_global_critical_section_leave();
	}

	// free wait
	if(thread->state == TASK_THREAD_STATE_ALARM)
		s_task_thread_remove_list(&thread->process->thread_alarm, thread, &thread->process->thread_mutex);
}

/*
 * release thread
 * NOTE: Be Care!We don't deal any mutex in operation, because we don't know how to deal it,unlock or continue lock?
 *		we don't know, so, we just keep locked in order to keep system running correctly!So, user should deal the lock
 *		before terminate a thread!MS do this as the same,^_^
 *		Also, we don't free user mode stack, because this should be done by runtime librarys.^_^.kernel can't know what
 *		library user used, so, kernel can't free it.*_*!
 */
void task_thread_releasethread(k_thread *thread)
{
	if(thread == NULL)
		return;

	// free system mode statck
	__mmu_os_vritual_free(thread->sys_stack, TASK_THREAD_SYS_STACK_SIZE>>12);

	// free mutex resouces.just simply do something!
	s_task_thread_free_wait(thread);
	
	// free tid resouce
	task_tid_release(thread->id);

	// free TCB now.
	kernel_free(thread);

	thread  = NULL;
}

/*
 *	real terminate thread, clear terminate thread list of a process
 * NOTE: This function need caller use global 
 * lock protection, but dont need lock if terminate process!
 * WARNING:this function only can be called in task_schedule or terminate process!
 */
void task_thread_terminate_handler(k_process *process)
{
	k_thread *thread = NULL;
	
	thread = process->thread_terminating;
	// delete from list
	if(thread)
		process->thread_terminating = thread->next;
	while(thread)
	{
		if(thread->type == TASK_THREAD_TYPE_MAIN)
		{
			// this is main thread of process, need to terminate process
			task_process_terminate(process);
			
			// we don;t need to do anything about this, process terminating will do. so ,just return;
			return;
		}
		task_thread_releasethread(thread);
		thread = process->thread_terminating;
		if(thread)
			process->thread_terminating = thread->next;
	}
}

/*
 *	create system default thread for default process
 *NOTE: Any other cann't call this function. this just used to create system default thread of default process.
 */
k_thread *task_thread_create_sysdefault(unsigned int code_addr)
{
	k_thread *thread = NULL;
	unsigned int *kernel_stack = NULL;
	unsigned int *ks_top = NULL;
	
	// create default thread.
	thread = kernel_allocate(sizeof(k_thread));
	if(thread == NULL)
	{
		// we just loop system, but can't do nothing ...
		while(1);
	}
	kernel_stack = __mmu_os_virtual_allocate(1);	// 4kb
	if(kernel_stack == NULL)
	{
		while(1);
	}
	task_thread_initTCB(thread);
	thread->sys_stack = (unsigned int)kernel_stack;
	ks_top = (unsigned int *)((unsigned int)kernel_stack + TASK_THREAD_SYS_STACK_SIZE);
	thread->kernel_sp = (unsigned int)task_thread_initialize_kernel_stack(ks_top, 0, 0);
	ks_top--;						// ret value
	ks_top--;						// R14, 我们再这里给14赋值为中断返回地址，这样子的话，在程序执行完传入的函数后，会跳转到中断回复的地方，从而
	*ks_top = e_thread_start_addr;	// 可以实现将第一个进程跳转到用户模式的地址空间去，从而实现用户模式空间第一个进程的创建^_^.详细，请画栈图研究！
	// 错误！！！注意：这里还没有分配用户态空间的线程的栈！！！！！！
	thread->kernel_sp = (unsigned int)task_thread_initialize_cpu_context((unsigned int *)thread->kernel_sp,code_addr);
	return thread;
}

/*
 * sleep, wait...
 *		this function make thread to sleep in a moment
 * time is in millionsections
 */
void task_thread_sleep(k_thread *thread, long long time)
{
	thread->alamtime = time + get_tick_count();
	task_thread_insert(thread, TASK_THREAD_STATE_ALARM);
}

/*
 * try to wake up sleeping thread
 */
void task_thread_trytowakeup(k_process *process)
{
	long long curtime = get_tick_count();
	k_thread *cur_thread = NULL;

	mt_mutex_lock(&process->thread_mutex,-1);

	cur_thread = process->thread_alarm;
	while(cur_thread)
	{
		if(cur_thread->alamtime <= curtime)
		{
			task_thread_insert(cur_thread, TASK_THREAD_STATE_RUNABLE);
			cur_thread->alamtime = 0;
		}
		cur_thread = cur_thread->next;
	}

	mt_mutex_unlock(&process->thread_mutex);
}

